﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Drawing;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using Vit.Common.Extensions;

namespace Vit.Report.XmlWebReport
{
    public class Report
    {
        public const string ConfigurationFilePath = "~/Reports/";

        public static Report CreateReport(string xmlFile, Dictionary<string, object> data, Func<string, object, string, string> getDataValue)
        {
            Report rpt = new Report();
            string topLeftPosition, bottomRightPosition, mapName, cssClass, style;

            XDocument doc = XDocument.Load(HttpContext.Current.Server.MapPath(ConfigurationFilePath + xmlFile));
            XElement rptEle = doc.Root;
            rpt.PageSetting.PageMargin = ReportHelper.ParseMargin(rptEle.AttributeValue("margin", "0,0,0,0"));
            rpt.PageSetting.PageOffset = ReportHelper.ParsePoint(rptEle.AttributeValue("offset", "0,0"));
            rpt.PageSetting.PageSize = ReportHelper.ParseSize(rptEle.AttributeValue("size", "210,297"));
            ReportHelper.ParseLocation(rptEle.AttributeValue("rect", "A1:P30"), out topLeftPosition, out bottomRightPosition);
            rpt.PageSetting.PageRectanglePosition = new RectanglePosition(topLeftPosition, bottomRightPosition);
            rpt.PageSetting.DefaultFont = ReportHelper.ParseFont(rptEle.AttributeValue("font", "宋体 4"));
            rpt.PageSetting.ShowWaterMarkGrid = rptEle.AttributeValue<bool>("showGrid", true);
            rpt.PageSetting.Unit = "mm";

            IReportRender renderer = null;
            var rendererName = rptEle.AttributeValue("renderer", "");
            if (!string.IsNullOrEmpty(rendererName))
            {
                Type type = Type.GetType(rendererName);
                renderer = Activator.CreateInstance(type) as IReportRender;
            }

            if (renderer == null)
            {
                //renderer = new TableReportRenderer();
                renderer = new DivReportRenderer();
            }

            var headEle = rptEle.Element("head");
            if (headEle != null && !string.IsNullOrEmpty(headEle.Value))
                rpt.HeadContent = headEle.Value;

            foreach (XElement pageEle in rptEle.Elements("page"))
            {
                Page page = new Page(rpt);
                page.Renderer = renderer;

                page.BackGroundImageUrl = pageEle.AttributeValue("backgroundImage", "");

                if (pageEle.Attribute("margin") != null)
                    page.PageMargin = ReportHelper.ParseMargin(pageEle.Attribute("margin").Value);
                else
                    page.PageMargin = rpt.PageSetting.PageMargin;
                if (pageEle.Attribute("rect") != null)
                {
                    ReportHelper.ParseLocation(pageEle.Attribute("value").Value, out topLeftPosition, out bottomRightPosition);
                    page.PageRectanglePosition = new RectanglePosition(topLeftPosition, bottomRightPosition);
                }
                else
                    page.PageRectanglePosition = rpt.PageSetting.ContentRectanglePosition.Clone();
                if (pageEle.Attribute("offset") != null)
                    page.PageOffset = ReportHelper.ParsePoint(pageEle.Attribute("offset").Value);
                else
                    page.PageOffset = rpt.PageSetting.PageOffset;
                if (pageEle.Attribute("size") != null)
                    page.PageSize = ReportHelper.ParseSize(pageEle.Attribute("size").Value);
                else
                    page.PageSize = rpt.PageSetting.PageSize;
                if (pageEle.Attribute("font") != null)
                    page.DefaultFont = ReportHelper.ParseFont(pageEle.Attribute("font").Value);
                else
                    page.DefaultFont = rpt.PageSetting.DefaultFont;
                if (pageEle.Attribute("showGrid") != null)
                    page.ShowWaterMarkGrid = pageEle.AttributeValue<bool>("showGrid", true);
                else
                    page.ShowWaterMarkGrid = rpt.PageSetting.ShowWaterMarkGrid;
                page.Unit = rpt.PageSetting.Unit;
                if (pageEle.Attribute("headerOffset") != null)
                    page.HeaderOffset = pageEle.AttributeValue<int>("headerOffset", 0);
                else
                    page.HeaderOffset = rpt.PageSetting.HeaderOffset;
                if (pageEle.Attribute("footerOffset") != null)
                    page.FooterOffset = pageEle.AttributeValue<int>("footerOffset", 0);
                else
                    page.FooterOffset = rpt.PageSetting.FooterOffset;

                foreach (XElement ele in pageEle.Elements())
                {
                    ReportHelper.ParseLocation(ele.AttributeValue("loc", ""), out topLeftPosition, out bottomRightPosition);
                    Font font = ReportHelper.ParseFont(ele.AttributeValue("font", null)) ?? rpt.PageSetting.DefaultFont;
                    cssClass = ele.AttributeValue("class", "");
                    style = ele.AttributeValue("style", "");
                    switch (ele.Name.LocalName)
                    {
                        case "ele":
                            string col;
                            ReportHelper.ParseData(ele.AttributeValue("data", null), out mapName, out col);
                            string val = ele.Value;
                            if (string.IsNullOrEmpty(val) && !string.IsNullOrEmpty(mapName) && !string.IsNullOrEmpty(col))
                                val = getDataValue(mapName, data[mapName], col);
                            var cell = page.NewElement(topLeftPosition, bottomRightPosition, val);
                            cell.Font = font;
                            if (!string.IsNullOrEmpty(ele.AttributeValue("positionType", "")))
                            {
                                cell.PositionType = (PositionType)Enum.Parse(typeof(PositionType), ele.AttributeValue("positionType", ""));
                            }
                            cell.CssClass += " " + cssClass;
                            cell.CssStyle = style;
                            PointF of1 = ReportHelper.ParsePoint(ele.AttributeValue("offset", "0,0"));
                            cell.Offset(of1.X, of1.Y);
                            SizeF sz1 = ReportHelper.ParseSize(ele.AttributeValue("append", "0,0"));
                            cell.Append(sz1.Width, sz1.Height);
                            if (!string.IsNullOrEmpty(ele.AttributeValue("id", "")))
                                cell.ID = ele.Attribute("id").Value;
                            cell.SplitTo = ele.AttributeValue("splitTo", "");
                            cell.LineHeight = ele.AttributeValue<double>("lineHeight", 1.0);
                            cell.LetterSpacing = ele.AttributeValue<double>("letterSpacing", 0.0);
                            cell.Editable = ele.AttributeValue<bool>("editable", false);
                            cell.IsHtml = ele.AttributeValue<bool>("isHtml", false);
                            cell.NoWrap = ele.AttributeValue<bool>("noWrap", false);
                            cell.Format = ele.AttributeValue("format", "{0}");
                            cell.IsHeader = ele.AttributeValue<bool>("isHeader", false);
                            cell.IsFooter = ele.AttributeValue<bool>("isFooter", false);

                            break;
                        case "table":
                            var tbl = page.NewTableElement(topLeftPosition, bottomRightPosition, "");
                            tbl.CssClass += " " + cssClass;
                            tbl.CssStyle = style;
                            tbl.CellCssClass += " " + ele.AttributeValue("cellClass", "");
                            tbl.CellCssStyle = ele.AttributeValue("cellStyle", "");
                            tbl.CellFont = ReportHelper.ParseFont(ele.AttributeValue("cellFont", ""));
                            PointF oft1 = ReportHelper.ParsePoint(ele.AttributeValue("offset", "0,0"));
                            tbl.Offset(oft1.X, oft1.Y);
                            SizeF szt1 = ReportHelper.ParseSize(ele.AttributeValue("append", "0,0"));
                            tbl.Append(szt1.Width, szt1.Height);
                            if (!string.IsNullOrEmpty(ele.AttributeValue("positionType", "")))
                            {
                                tbl.PositionType = (PositionType)Enum.Parse(typeof(PositionType), ele.AttributeValue("positionType", ""));
                            }

                            if (ele.Attribute("fixRows") != null)
                                tbl.FixRows = Convert.ToInt32(ele.Attribute("fixRows").Value);

                            tbl.MsgWhenRowsExceedFixRows = ele.AttributeValue("msgWhenRowsExceedFixRows", "");

                            tbl.HasBorders = ele.AttributeValue<bool>("hasBorders", true);
                            tbl.RowSpan = ele.AttributeValue<int>("rowSpan", 1);
                            tbl.IsHeader = ele.AttributeValue<bool>("isHeader", false);
                            tbl.IsFooter = ele.AttributeValue<bool>("isFooter", false);
                            tbl.CopyHeaderWhenSplit = ele.AttributeValue<bool>("copyHeaderWhenSplit", false);

                            foreach (XElement headerEle in ele.Element("headers").Elements("header"))
                            {
                                ReportHelper.ParseLocation(headerEle.AttributeValue("loc", ""), out topLeftPosition, out bottomRightPosition);
                                var header = tbl.NewTableHeaderElement(topLeftPosition, bottomRightPosition, headerEle.AttributeValue("column", ""), headerEle.Value);
                                if (!string.IsNullOrEmpty(headerEle.AttributeValue("positionType", "")))
                                {
                                    header.PositionType = (PositionType)Enum.Parse(typeof(PositionType), headerEle.AttributeValue("positionType", ""));
                                }
                                header.Font = ReportHelper.ParseFont(headerEle.AttributeValue("font", null)) ?? font;
                                header.CssClass += " " + headerEle.AttributeValue("class", "");
                                header.CssStyle = headerEle.AttributeValue("style", "");
                                header.LineHeight = headerEle.AttributeValue<double>("lineHeight", 1.0);
                                header.LetterSpacing = headerEle.AttributeValue<double>("letterSpacing", 0.0);
                                header.Editable = headerEle.AttributeValue<bool>("editable", false);
                                header.IsHtml = headerEle.AttributeValue<bool>("isHtml", false);
                                header.NoWrap = headerEle.AttributeValue<bool>("noWrap", false);
                                header.CellCssClass += " " + headerEle.AttributeValue("cellClass", "");
                                header.CellCssStyle = headerEle.AttributeValue("cellStyle", "");
                                header.CellFont = ReportHelper.ParseFont(headerEle.AttributeValue("cellFont", ""));
                                PointF ofh1 = ReportHelper.ParsePoint(headerEle.AttributeValue("offset", "0,0"));
                                header.Offset(ofh1.X, ofh1.Y);
                                SizeF szh1 = ReportHelper.ParseSize(headerEle.AttributeValue("append", "0,0"));
                                header.Append(szh1.Width, szh1.Height);
                            }

                            mapName = ele.AttributeValue("data", "");
                            if (string.IsNullOrEmpty(mapName))
                                throw new Exception("必须为table配置节设置data属性");

                            IEnumerable ds = data[mapName] as IEnumerable;
                            tbl.SetData(ds, mapName, getDataValue);

                            break;
                        case "style":
                            page.CssStyles += ele.Value;
                            break;
                        case "script":
                            page.Javascripts += ele.Value;
                            break;
                    }
                }

                rpt.Pages.Add(page);
                page.PageNo = rpt.Pages.Count;
            }

            return rpt;
        }

        public Report()
        {
            Pages = new List<Page>();
            PageSetting = new Page(this);
            Data = new Dictionary<string, object>();
        }

        public void Calculate(out float xOffset, out float yOffset)
        {
            xOffset = 0;
            yOffset = 0;
            foreach (Page page in Pages)
            {
                page.Move(0, yOffset);
                page.CalculatePosition();
                float pageX = 0, pageY = 0;
                GetPageOffset(page, out pageX, out pageY);
                if (xOffset < pageX)
                    xOffset = pageX;
                if (yOffset < pageY)
                    yOffset = pageY;
            }
        }

        private void GetPageOffset(Page page, out float xOffset, out float yOffset)
        {
            xOffset = 0;
            yOffset = 0;
            Stack<Page> st = new Stack<Page>();
            st.Push(page);
            while (st.Count > 0)
            {
                Page p = st.Pop();
                yOffset += page.PageSize.Height + page.PageOffset.Y;

                if (p.ExtraYPage != null && p.ExtraYPage.Elements.Count > 0)
                    st.Push(p.ExtraYPage);
            }

            //st.Push(page);
            //while (st.Count > 0)
            //{
            //    Page p = st.Pop();
            //    xOffset += page.PageSize.Width + page.PageOffset.X;

            //    if (p.ExtraXPage != null && p.ExtraXPage.Elements.Count > 0)
            //        st.Push(p.ExtraXPage);
            //}
        }

        public void Render(TextWriter writer)
        {
            float xOffset = 0;
            float yOffset = 0;
            Calculate(out xOffset, out yOffset);
            PageCount = 0;

            foreach (Page page in Pages)
            {
                page.PageIndex = PageCount++;
                Page extraPage = page.ExtraXPage;
                while (extraPage != null)
                {
                    extraPage.PageIndex = PageCount++;
                    extraPage = extraPage.ExtraXPage;
                }
                extraPage = page.ExtraYPage;
                while (extraPage != null)
                {
                    extraPage.PageIndex = PageCount++;
                    extraPage = extraPage.ExtraYPage;
                }
            }

            foreach (Page page in Pages)
                page.Render(writer);
        }

        public void RenderHead(TextWriter writer)
        {
            writer.Write(HeadContent);
        }

        public void Merge(Report report)
        {
            int totalPages = this.Pages.Count;
            foreach (Page page in report.Pages)
            {
                totalPages++;
                page.PageNo = totalPages;
                this.Pages.Add(page);
            }
        }

        public Func<string, object, string, string> GetDataValue;

        public List<Page> Pages { get; set; }
        public Page PageSetting { get; set; }
        public Dictionary<string, object> Data { get; set; }
        public string HeadContent { get; set; }

        public int PageCount
        {
            get;
            set;
        }

    }
}
